package com.lzq.course.file.controller.admin;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.vod.model.v20170321.GetMezzanineInfoResponse;
import com.lzq.course.server.dto.FileDto;
import com.lzq.course.server.dto.ResponseDto;
import com.lzq.course.server.enums.FileUseEnum;
import com.lzq.course.server.service.FileService;
import com.lzq.course.server.util.Base64ToMultipartFile;
import com.lzq.course.server.util.UuidUtil;
import com.lzq.course.server.util.VodUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;

/**
 * 上传
 * 上传成功后合并文件片成一个
 */
@RestController
@RequestMapping("/admin")
public class UploadController {

    private final static Logger LOG = LoggerFactory.getLogger(UploadController.class);

    public static final String BUSINESS_NAME = "文件上传";

    @Value("${file.path}")
    private String FILE_PATH; // 本地地址

    @Value("${file.domain}")
    private String FILE_DOMAIN; // 前端文件访问地址

    @Value("${vod.accessKeyId}")
    private String accessKeyId;

    @Value("${vod.accessKeySecret}")
    private String accessKeySecret;

    @Resource
    private FileService fileService;

    /**
     * 上传文件
     * 文件前端转为了base64传输（不想用MultipartFile接收 只能这样转为Base64对象接收）
     * @return
     */
    @RequestMapping("/upload")
    public ResponseDto upload(@RequestBody FileDto fileDto) {
        ResponseDto responseDto = new ResponseDto();
        LOG.info("文件上传开始");
        String use = fileDto.getUse();
        String suffix = fileDto.getSuffix();
        String key64 = fileDto.getKey();
        MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(fileDto.getShard());

        FileUseEnum useEnum = FileUseEnum.getByCode(use); // 用途
        String dir = useEnum.name().toLowerCase(); // course or teacher

        // 判断文件夹是否存在
        File fullDir = new File(FILE_PATH + dir); // E:\server_upload_file\course\
        if (!fullDir.exists()) { // 文件夹不存在则创建
            fullDir.mkdirs();
        }

        String path = new StringBuilder(dir)
                .append(File.separator)
                .append(key64)
                .append(".")
                .append(suffix)
                .toString(); // course\teacher\xxxx.jpg
        String localPath = new StringBuilder(path)
                .append(".")
                .append(fileDto.getShardIndex())
                .toString(); // course\teacher\xxxx.jpg.1...2...3...

        // 文件全路径
        String fullPath = FILE_PATH + localPath; //E:\server_upload_file\course\teacher\xxxx.jpg.1
        File dest = new File(fullPath);
        try {
            shard.transferTo(dest); // 文件上传
        } catch (IOException e) {
            e.printStackTrace();
        }

        LOG.info("保存文件记录开始");
        fileDto.setPath(path); // 相对路径
        fileService.save(fileDto);

        // 如果是最后一个片 合并
        if (fileDto.getShardIndex().equals(fileDto.getShardTotal())) {
            try {
                merge(fileDto);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        fileDto.setPath(FILE_DOMAIN + path); // 文件路径给前端
        responseDto.setContent(fileDto);
        return responseDto;
    }

    /**
     * 检查分片文件状态 shardIndex
     * 判断是保存在视频点播 还是 本地
     * @param key 文件加密 key
     * @return
     */
    @GetMapping("/check/{key}")
    public ResponseDto check(@PathVariable String key) throws Exception {
        ResponseDto responseDto = new ResponseDto();
        FileDto fileDto = fileService.findByKey(key);
        if (fileDto != null) {
            if (StringUtils.isEmpty(fileDto.getVod())) {
                // 视频存储在本地
                fileDto.setPath(FILE_DOMAIN + fileDto.getPath());
            } else {
                // 视频存储在vod 那么获取视频点播地址。
                DefaultAcsClient vodClient = VodUtil.initVodClient(accessKeyId, accessKeySecret);
                GetMezzanineInfoResponse info = VodUtil.getMezzanineInfo(vodClient, fileDto.getVod());
                String fileURL = info.getMezzanine().getFileURL();
                fileDto.setPath(fileURL);
            }
        }
        responseDto.setContent(fileDto);

        return responseDto;
    }

    /**
     * 合并文件片
     * 删除分片
     * @param fileDto
     */
    private void merge(FileDto fileDto) throws InterruptedException {
        int shardTotal = fileDto.getShardTotal();

        LOG.info("合并文件开始");
        String localPath = fileDto.getPath(); // course\teacher\xxxx.jpg
        String fullPath = FILE_PATH + localPath; //E:\server_upload_file\course\teacher\xxxx.jpg
        File newFile = new File(fullPath);
        FileInputStream fis = null;
        FileOutputStream fos = null;

        int len = 0;
        byte[] buffer = new byte[10 * 1024 * 1024];

        try {
            fos = new FileOutputStream(newFile, true);// 文件可追加
            for (int i = 0; i < fileDto.getShardTotal(); i++) {
                fis = new FileInputStream(fullPath + "." + (i + 1)); // 按顺序合并
                while((len = fis.read(buffer)) != -1) { // 读
                    fos.write(buffer, 0, len); // 写
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }if (fos != null) {
                    fos.close();
                }
                LOG.info("IO流关闭");
            } catch (Exception e) {
                LOG.error("IO流关闭", e);
            }
        }

        LOG.info("合并文件结束");

        System.gc(); // 告诉回收垃圾 才可以删除文件 防止占用
        Thread.sleep(500); // 其他事做完在解除占用文件

        // 删除分片
        LOG.info("删除分片开始");
        for (int i = 0; i < shardTotal; i++) {
            String filePath = FILE_PATH + localPath + "." + (i + 1);
            File file = new File(filePath);
            boolean result = file.delete();
            LOG.info("删除{}，{}", filePath, result ? "成功" : "失败");
        }
        LOG.info("删除分片结束");
    }
}
